{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Using and Creating Labels\n",
    "\n",
    "Labels are a way to 'bookmark' certain pores for easier lookup later, such as specifying boundary conditions.  When networks are generated they include a set of relevent labels that have been added for convenience.  It's also possible for users to add their own labels.  This tutorial will cover how they work, how to add them, and how to use them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:28.275200Z",
     "iopub.status.busy": "2021-06-24T11:25:28.273574Z",
     "iopub.status.idle": "2021-06-24T11:25:28.851067Z",
     "shell.execute_reply": "2021-06-24T11:25:28.849606Z"
    }
   },
   "outputs": [],
   "source": [
    "import openpnm as op\n",
    "%config InlineBackend.figure_formats = ['svg']\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using Pre-defined Labels\n",
    "\n",
    "The simple ``Cubic`` network has several labels added during creating.  We can see these labels when we inspecting (i.e. ``print``) the network,"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:28.863289Z",
     "iopub.status.busy": "2021-06-24T11:25:28.858460Z",
     "iopub.status.idle": "2021-06-24T11:25:28.869176Z",
     "shell.execute_reply": "2021-06-24T11:25:28.870322Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "openpnm.network.Cubic : net_01\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "#     Properties                                    Valid Values\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "1     pore.coords                                      25 / 25   \n",
      "2     throat.conns                                     40 / 40   \n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "#     Labels                                        Assigned Locations\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "1     pore.all                                      25        \n",
      "2     pore.back                                     5         \n",
      "3     pore.front                                    5         \n",
      "4     pore.internal                                 25        \n",
      "5     pore.left                                     5         \n",
      "6     pore.right                                    5         \n",
      "7     pore.surface                                  16        \n",
      "8     throat.all                                    40        \n",
      "9     throat.internal                               40        \n",
      "10    throat.surface                                16        \n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "pn = op.network.Cubic(shape=[5, 5, 1])\n",
    "print(pn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the above output we can see 2 'properties', which is the term used for numerical data, and 10 'labels'.  Labels are also Numpy arrays, so are stored in the OpenPNM objects along 'propery' arrays. The different is that 'labels' are ``boolean`` type (i.e. ``True``, ``False``).\n",
    "\n",
    "A ``boolean`` array, such as *'pore.left'* is an *Np* long with ``True`` values indicating which pores possess that label.  \n",
    "\n",
    "The label 'pore.left' clearly indicates which pores on of the 'left' side of the network.  We can inspect the label array directly:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:28.879350Z",
     "iopub.status.busy": "2021-06-24T11:25:28.877473Z",
     "iopub.status.idle": "2021-06-24T11:25:28.882808Z",
     "shell.execute_reply": "2021-06-24T11:25:28.883962Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ True  True  True  True  True False False False False False False False\n",
      " False False False False False False False False False False False False\n",
      " False]\n"
     ]
    }
   ],
   "source": [
    "print(pn['pore.left'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But it's more useful to extract the *locations* of the true values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:28.905976Z",
     "iopub.status.busy": "2021-06-24T11:25:28.904638Z",
     "iopub.status.idle": "2021-06-24T11:25:28.910088Z",
     "shell.execute_reply": "2021-06-24T11:25:28.911274Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 2, 3, 4])"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    
    "np.where(pn['pore.left'])[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "OpenPNM objects all have a method for accessing such locations directly without calling ``where``:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:28.918912Z",
     "iopub.status.busy": "2021-06-24T11:25:28.917610Z",
     "iopub.status.idle": "2021-06-24T11:25:28.922174Z",
     "shell.execute_reply": "2021-06-24T11:25:28.923197Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 2, 3, 4])"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn.pores('left')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The advantage of the using the OpenPNM method is that you can create more complex queries, such as pores that are 'left' *or* 'back':"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:28.931413Z",
     "iopub.status.busy": "2021-06-24T11:25:28.930382Z",
     "iopub.status.idle": "2021-06-24T11:25:28.935482Z",
     "shell.execute_reply": "2021-06-24T11:25:28.936258Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0,  1,  2,  3,  4,  9, 14, 19, 24])"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn.pores(['left', 'back'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Or, pores that are *both* left *and* back:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:28.945848Z",
     "iopub.status.busy": "2021-06-24T11:25:28.944723Z",
     "iopub.status.idle": "2021-06-24T11:25:28.951157Z",
     "shell.execute_reply": "2021-06-24T11:25:28.949904Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([4])"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pn.pores(['left', 'back'], mode='and')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Because this is a 2D network, we only found one 'corner' pore ([0]).  In 3D this would have found all the pores on the 'edge'."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Points to Note about Using Labels\n",
    "\n",
    "* Labels can be applied to throats in exactly the same way as decribed here for pores.\n",
    "* Every OpenPNM object has a ``pores`` and a ``throats`` method that can be used to retrieve the pore or throat indices  where a label or combination of labels has been applied.\n",
    "* For internal purposes, every OpenPNM object has pre-defined labels called 'pore.all' and 'throat.all'.  These are ``True`` everywhere and are used internally for determining the number of pore and throats on an object. \n",
    "* The format of creating an entire ND-array to label just a few locations may seem a bit wasteful of data storage since many ``False`` values are created to indicate pores do *not* have a label.  However, such ``boolean`` area can be used to index into Numpy arrays.\n",
    "* Labels are used throughout any given script, particularly for specifying boundary conditions, so are an important feature to understand."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining Custom Labels\n",
    "\n",
    "It is common in OpenPNM scripts to do some complex searching to find all pores that satisfy some set of conditions.  It can be helpful to label these for later use.  There are two ways to do this, by directly creating a boolean mask and storing it, or by calling the ``set_label`` method."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Creating Boolean Masks\n",
    "Assume we want to label the 'corner' pore we found above.  First catch the result in a variable:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:28.960288Z",
     "iopub.status.busy": "2021-06-24T11:25:28.958854Z",
     "iopub.status.idle": "2021-06-24T11:25:28.962548Z",
     "shell.execute_reply": "2021-06-24T11:25:28.963715Z"
    }
   },
   "outputs": [],
   "source": [
    "Ps = pn.pores(['left', 'back'], mode='and')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then create an empty label array (filled with ``False`` values) on the network:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:28.972617Z",
     "iopub.status.busy": "2021-06-24T11:25:28.971467Z",
     "iopub.status.idle": "2021-06-24T11:25:28.977451Z",
     "shell.execute_reply": "2021-06-24T11:25:28.976405Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[False False False False False False False False False False False False\n",
      " False False False False False False False False False False False False\n",
      " False]\n"
     ]
    }
   ],
   "source": [
    "pn['pore.corner'] = False\n",
    "print(pn['pore.corner'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now insert ``True`` values at the desired array indices corresponding to the pore location:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:28.987387Z",
     "iopub.status.busy": "2021-06-24T11:25:28.986065Z",
     "iopub.status.idle": "2021-06-24T11:25:28.991077Z",
     "shell.execute_reply": "2021-06-24T11:25:28.992214Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[4]\n"
     ]
    }
   ],
   "source": [
    "pn['pore.corner'][Ps] = True\n",
    "print(pn.pores('corner'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Using ``set_label``\n",
    "The process of creating an empty label array, then filling the ``True`` values is a bit annoying, so there is a helper/shortcut method on all OpenPNM objects.  It can be used to create a new label, or to add to an existing one.  Let's label another corner pore as 'pore.corner':"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:29.002789Z",
     "iopub.status.busy": "2021-06-24T11:25:29.001522Z",
     "iopub.status.idle": "2021-06-24T11:25:29.007027Z",
     "shell.execute_reply": "2021-06-24T11:25:29.005960Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 4]\n"
     ]
    }
   ],
   "source": [
    "Ps = Ps = pn.pores(['left', 'front'], mode='and')\n",
    "pn.set_label(label='corner', pores=Ps)\n",
    "print(pn.pores('corner'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``set_label`` method has a ``mode`` argument that can be used to change the behavior, such as ``'add'`` or ``'remove'`` labels from the given locations (default is ``mode='add'``), ``'purge'`` all existing indices from the given label, or ``'remove'`` which purges then adds the given pores to the specified label.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:25:29.015997Z",
     "iopub.status.busy": "2021-06-24T11:25:29.014698Z",
     "iopub.status.idle": "2021-06-24T11:25:29.019568Z",
     "shell.execute_reply": "2021-06-24T11:25:29.020712Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "False\n"
     ]
    }
   ],
   "source": [
    "pn.set_label(label='pore.corner', mode='purge')\n",
    "print('pore.corner' in pn.keys())"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
